#include <AST/type/types.hpp>
#include <llvm/IR/DerivedTypes.h>
#include <global.hpp>
#include <llvm/IR/Instruction.h>
#include <AST/ExprAST.hpp>
#include <AST/expr/literal/BoolAST.hpp>
#include <AST/expr/literal/CharAST.hpp>
#include <AST/expr/literal/FloatAST.hpp>
#include <AST/expr/literal/IntAST.hpp>
#include <AST/expr/literal/StringPtrAST.hpp>

namespace std {
    string to_string(OpType opType) {
        switch (opType) {
            case OpType::Add:
                return "+";
            case OpType::Sub:
                return "-";
            case OpType::Mul:
                return "*";
            case OpType::Div:
                return "/";
            case OpType::Mod:
                return "%";
            case OpType::Lt:
                return "<";
            case OpType::Gt:
                return ">";
            case OpType::Leq:
                return "<=";
            case OpType::Geq:
                return ">=";
            case OpType::Eq:
                return "=";
            case OpType::Neq:
                return "!=";
            case OpType::And:
                return "&&";
            case OpType::Or:
                return "||";
            case OpType::Xor:
                return "^";
            case OpType::Assign:
                return "=";
            case OpType::Semi:
                return ";";
            default:
                return "Undefined";
        }
    }

} // namespace std

namespace BasicType {

    Type *const FloatTy = Float();
    Type *const IntTy = Int();
    Type *const CharTy = Char();
    Type *const BoolTy = Bool();
    Type *const VoidTy = Void();
    // 请使用 Type *String(uint64_t length);
    // Type *const StringPtrTy = StringPtr();
    Type *const DoubleTy = FloatTy;

    Type *Void() {
        static Type *const type = Type::getVoidTy(theContext);
        return type;
    }

    Type *Bool() {
        static llvm::IntegerType *const type = Type::getInt1Ty(theContext);
        return type;
    }

    Type *Char() {
        static llvm::IntegerType *const type = Type::getInt8Ty(theContext);
        return type;
    }

    Type *Int() {
        static llvm::IntegerType *const type = Type::getInt32Ty(theContext);
        return type;
    }

    Type *Float() {
        static Type *const type = Type::getDoubleTy(theContext);
        return type;
    }

    Type *String(uint64_t length) {
        return llvm::ArrayType::get(CharTy, length);
    }

    Type *String(Expr *lengthConstexpr) {
        const uint64_t length =
            llvm::dyn_cast<llvm::ConstantInt>(lengthConstexpr->codegen())
                ->getZExtValue();
        return llvm::ArrayType::get(CharTy, length);
    }

    // 所有的sign判断，最终依赖此函数
    bool isSigned(const Type *type) { return type == Int() || type == Float(); }

    bool isUnsigned(const Type *type) { return !isSigned(type); }

    bool isSigned(const Value *value) { return isSigned(value->getType()); }

    bool isUnsigned(const Value *value) { return isUnsigned(value->getType()); }

    Value *getDefault(Type *type) {
        if (type == BoolTy) return ::Bool::getDefault();
        if (type == CharTy) return ::Char::getDefault();
        if (type == IntTy) return ::Int::getDefault();
        if (type == FloatTy) return ::Float::getDefault();
        // if (type == StringTy)
        logWarning("BasicType::getDefault: undefined type");
        if (type->isAggregateType()) return llvm::UndefValue::get(type);
        return nullptr;
    }

    Type *ArrayToPtr(Type *ArrayTy) {
        if (ArrayTy == nullptr || !ArrayTy->isArrayTy()) return nullptr;
        return ArrayTy->getArrayElementType()->getPointerTo();
    }

    bool isEqualAsPtr(Type *type0, Type *type1) {
        if (type0->isArrayTy() && type1->isArrayTy()) return type0 == type1;
        if (type0->isArrayTy()) type0 = type0->getArrayElementType();
        else if (type1->isArrayTy())
            type1 = type1->getArrayElementType();
        return type0 == type1;
    }

    bool isPtrElementEqualAsPtr(Type *type0, Type *type1) {
        if (!type0->isPointerTy() || !type1->isPointerTy()) return false;
        return isEqualAsPtr(
            type0->getPointerElementType(), type1->getPointerElementType());
    }

    // 最坏只有数组长度不同，且其中一个长度为0
    bool isPtrArraySizeDifferentOnly(Type *type0, Type *type1) {
        if (type0 == type1) return true;
        if (!type0->isPointerTy() || !type1->isPointerTy()) return false;
        type0 = type0->getPointerElementType();
        type1 = type1->getPointerElementType();
        if (!type0->isArrayTy() || !type1->isArrayTy()) return false;
        if (type0->getArrayElementType() == type1->getArrayElementType()
            && (type0->getArrayNumElements() == 0
                || type1->getArrayNumElements() == 0))
            return true;
        return false;
    }
} // namespace BasicType

namespace BasicConvert {

    CastOps getCastOp(const Value *from, Type *to) {
        return llvm::CastInst::getCastOpcode(
            from, BasicType::isSigned(from), to, BasicType::isSigned(to));
    }

    Value *toType(Type *destTy, Value *value, IRBuilder &builder) {
        if (value->getType() == destTy) return value;
        const CastOps castOp = getCastOp(value, destTy);
        return builder.CreateCast(castOp, value, destTy, "castTmp");
    }

    Type *binaryEnhance(Value *&lhs, Value *&rhs, IRBuilder &builder) {
        using namespace BasicType;
        Type *const lType = lhs->getType();
        Type *const rType = rhs->getType();
        assert(lType != VoidTy);
        assert(rType != VoidTy);
        if (lType == rType) return lType;
        // 转换优先级
        Type *const priority[] = {FloatTy, IntTy, CharTy, BoolTy};
        for (Type *const type : priority)
            if (lType == type || rType == type) {
                lhs = toType(type, lhs, builder);
                rhs = toType(type, rhs, builder);
                return type;
            }
        assert(false && "binaryEnhance failed: undefined situation");
        return nullptr;
    }
} // namespace BasicConvert
